﻿// -------------------------------------------------------------------------------------------------------------------------------
// <copyright file="CMFlattenDenseMatrixExtensions.cs" company="Exul">
//     Copyright (c) Exul. All rights reserved.
// </copyright>
// <author>
//     Exul
// </author>
// -------------------------------------------------------------------------------------------------------------------------------

namespace ExulLibrary.Mathematics.LinearAlgebra.Sequential.DecimalExtensions
{
    #region Usings

    using System;
    using System.Globalization;

    using ExulLibrary.Exceptions;

    #endregion

    /// <summary>
    ///     Provides extension methods for <see cref="CMFlattenDenseMatrix{T}"/> with <see cref="decimal"/> type parameter.
    /// </summary>
    public static class CMFlattenDenseMatrixExtensions
    {
        #region Methods

        #region public

        /// <summary>
        ///     Adds the specified factor to all elements of the matrix.
        /// </summary>
        /// <param name="matrix">
        ///     The matrix whose elements should be added.
        /// </param>
        /// <param name="factor">
        ///     The factor to add.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="matrix"/> is <see langword="null"/>.
        /// </exception>
        public static void Add(this CMFlattenDenseMatrix<decimal> matrix, decimal factor)
        {
#if SAFE

            if (matrix == null)
            {
                throw new ArgumentNullException("matrix");
            }

#endif

            var values = matrix.Values;

            var elementsCount = matrix.RowsCount * matrix.ColumnsCount;
            for (var elementIndex = 0; elementIndex < elementsCount; elementIndex++)
            {
                values[elementIndex] += factor;
            }
        }

        /// <summary>
        ///     Adds the second matrix to the first matrix.
        /// </summary>
        /// <param name="augend">
        ///     The augend matrix.
        /// </param>
        /// <param name="addend">
        ///     The addend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="augend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="addend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="addend"/> to add to <paramref name="augend"/> is determined as
        ///     min(<paramref name="augend"/>.RowsCount, <paramref name="addend"/>.RowsCount).
        ///     The number of columns of <paramref name="addend"/> to add to <paramref name="augend"/> is determined as
        ///     min(<paramref name="augend"/>.ColumnsCount, <paramref name="addend"/>.ColumnsCount).
        /// </remarks>
        public static void Add(this CMFlattenDenseMatrix<decimal> augend, CMFlattenDenseMatrix<decimal> addend)
        {
#if SAFE

            if (augend == null)
            {
                throw new ArgumentNullException("augend");
            }

            if (addend == null)
            {
                throw new ArgumentNullException("addend");
            }

#endif

            var augendValues = augend.Values;
            var addendValues = addend.Values;
            var augendRowsCount = augend.RowsCount;
            var addendRowsCount = addend.RowsCount;
            var rowsCountToAdd = Math.Min(augendRowsCount, addendRowsCount);

            var columnsCountToAdd = Math.Min(augend.ColumnsCount, addend.ColumnsCount);
            for (var columnIndex = 0; columnIndex < columnsCountToAdd; columnIndex++)
            {
                var addendElementIndex = addendRowsCount * columnIndex;

                var augendElementIndex = augendRowsCount * columnIndex;
                var columnEnd = augendElementIndex + rowsCountToAdd;
                while (augendElementIndex < columnEnd)
                {
                    augendValues[augendElementIndex] += addendValues[addendElementIndex];

                    augendElementIndex++;
                    addendElementIndex++;
                }
            }
        }

        /// <summary>
        ///     Adds the second matrix to the first matrix multiplied by the factor.
        /// </summary>
        /// <param name="augend">
        ///     The augend matrix.
        /// </param>
        /// <param name="augendFactor">
        ///     The factor of the augend matrix.
        /// </param>
        /// <param name="addend">
        ///     The addend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="augend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="addend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="addend"/> to add to <paramref name="augend"/> is determined as
        ///     min(<paramref name="augend"/>.RowsCount, <paramref name="addend"/>.RowsCount).
        ///     The number of columns of <paramref name="addend"/> to add to <paramref name="augend"/> is determined as
        ///     min(<paramref name="augend"/>.ColumnsCount, <paramref name="addend"/>.ColumnsCount).
        /// </remarks>
        public static void Add(
            this CMFlattenDenseMatrix<decimal> augend, decimal augendFactor, CMFlattenDenseMatrix<decimal> addend)
        {
#if SAFE

            if (augend == null)
            {
                throw new ArgumentNullException("augend");
            }

            if (addend == null)
            {
                throw new ArgumentNullException("addend");
            }

#endif

            var augendValues = augend.Values;
            var addendValues = addend.Values;
            var augendRowsCount = augend.RowsCount;
            var addendRowsCount = addend.RowsCount;
            var rowsCountToAdd = Math.Min(augendRowsCount, addendRowsCount);

            var columnsCountToAdd = Math.Min(augend.ColumnsCount, addend.ColumnsCount);
            for (var columnIndex = 0; columnIndex < columnsCountToAdd; columnIndex++)
            {
                var addendElementIndex = addendRowsCount * columnIndex;

                var augendElementIndex = augendRowsCount * columnIndex;
                var columnEnd = augendElementIndex + rowsCountToAdd;
                while (augendElementIndex < columnEnd)
                {
                    augendValues[augendElementIndex] =
                        (augendFactor * augendValues[augendElementIndex]) + addendValues[addendElementIndex];

                    augendElementIndex++;
                    addendElementIndex++;
                }
            }
        }

        /// <summary>
        ///     Adds the second matrix multiplied by the factor to the first matrix.
        /// </summary>
        /// <param name="augend">
        ///     The augend matrix.
        /// </param>
        /// <param name="addend">
        ///     The addend matrix.
        /// </param>
        /// <param name="addendFactor">
        ///     The factor of the addend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="augend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="addend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="addend"/> to add to <paramref name="augend"/> is determined as
        ///     min(<paramref name="augend"/>.RowsCount, <paramref name="addend"/>.RowsCount).
        ///     The number of columns of <paramref name="addend"/> to add to <paramref name="augend"/> is determined as
        ///     min(<paramref name="augend"/>.ColumnsCount, <paramref name="addend"/>.ColumnsCount).
        /// </remarks>
        public static void Add(
            this CMFlattenDenseMatrix<decimal> augend, CMFlattenDenseMatrix<decimal> addend, decimal addendFactor)
        {
#if SAFE

            if (augend == null)
            {
                throw new ArgumentNullException("augend");
            }

            if (addend == null)
            {
                throw new ArgumentNullException("addend");
            }

#endif

            var augendValues = augend.Values;
            var addendValues = addend.Values;
            var augendRowsCount = augend.RowsCount;
            var addendRowsCount = addend.RowsCount;
            var rowsCountToAdd = Math.Min(augendRowsCount, addendRowsCount);

            var columnsCountToAdd = Math.Min(augend.ColumnsCount, addend.ColumnsCount);
            for (var columnIndex = 0; columnIndex < columnsCountToAdd; columnIndex++)
            {
                var addendElementIndex = addendRowsCount * columnIndex;

                var augendElementIndex = augendRowsCount * columnIndex;
                var columnEnd = augendElementIndex + rowsCountToAdd;
                while (augendElementIndex < columnEnd)
                {
                    augendValues[augendElementIndex] += addendFactor * addendValues[addendElementIndex];

                    augendElementIndex++;
                    addendElementIndex++;
                }
            }
        }

        /// <summary>
        ///     Adds the second matrix multiplied by the second factor to the first matrix multiplied by the first factor.
        /// </summary>
        /// <param name="augend">
        ///     The augend matrix.
        /// </param>
        /// <param name="augendFactor">
        ///     The factor of the augend matrix.
        /// </param>
        /// <param name="addend">
        ///     The addend matrix.
        /// </param>
        /// <param name="addendFactor">
        ///     The factor of the addend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="augend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="addend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="addend"/> to add to <paramref name="augend"/> is determined as
        ///     min(<paramref name="augend"/>.RowsCount, <paramref name="addend"/>.RowsCount).
        ///     The number of columns of <paramref name="addend"/> to add to <paramref name="augend"/> is determined as
        ///     min(<paramref name="augend"/>.ColumnsCount, <paramref name="addend"/>.ColumnsCount).
        /// </remarks>
        public static void Add(
            this CMFlattenDenseMatrix<decimal> augend,
            decimal augendFactor,
            CMFlattenDenseMatrix<decimal> addend,
            decimal addendFactor)
        {
#if SAFE

            if (augend == null)
            {
                throw new ArgumentNullException("augend");
            }

            if (addend == null)
            {
                throw new ArgumentNullException("addend");
            }

#endif

            var augendValues = augend.Values;
            var addendValues = addend.Values;
            var augendRowsCount = augend.RowsCount;
            var addendRowsCount = addend.RowsCount;
            var rowsCountToAdd = Math.Min(augendRowsCount, addendRowsCount);

            var columnsCountToAdd = Math.Min(augend.ColumnsCount, addend.ColumnsCount);
            for (var columnIndex = 0; columnIndex < columnsCountToAdd; columnIndex++)
            {
                var addendElementIndex = addendRowsCount * columnIndex;

                var augendElementIndex = augendRowsCount * columnIndex;
                var columnEnd = augendElementIndex + rowsCountToAdd;
                while (augendElementIndex < columnEnd)
                {
                    augendValues[augendElementIndex] =
                        (augendFactor * augendValues[augendElementIndex]) + (addendFactor * addendValues[addendElementIndex]);

                    augendElementIndex++;
                    addendElementIndex++;
                }
            }
        }

        /// <summary>
        ///     Adds the second matrix to the specified elements of the first matrix.
        /// </summary>
        /// <param name="augend">
        ///     The augend matrix.
        /// </param>
        /// <param name="augendRowsIndexes">
        ///     The collection of zero-based indexes of rows of augend elements.
        /// </param>
        /// <param name="augendColumnsIndexes">
        ///     The collection of zero-based indexes of columns of augend elements.
        /// </param>
        /// <param name="addend">
        ///     The addend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="augend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="addend"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="augendRowsIndexes"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="augendColumnsIndexes"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="ElementIndexIsOutOfRangeException">
        ///     An element of <paramref name="augendRowsIndexes"/> is negative or greater than or equal to
        ///     rows count of <paramref name="augend"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         An element of <paramref name="augendColumnsIndexes"/> is negative or greater than or equal to
        ///         columns count of <paramref name="augend"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="addend"/> to add is determined as
        ///     min(<paramref name="augendRowsIndexes"/>.Length, <paramref name="addend"/>.RowsCount).
        ///     The number of columns of <paramref name="addend"/> to add is determined as
        ///     min(<paramref name="augendColumnsIndexes"/>.Length, <paramref name="addend"/>.ColumnsCount).
        /// </remarks>
        public static void Add(
            this CMFlattenDenseMatrix<decimal> augend,
            int[] augendRowsIndexes,
            int[] augendColumnsIndexes,
            CMFlattenDenseMatrix<decimal> addend)
        {
#if SAFE

            if (augend == null)
            {
                throw new ArgumentNullException("augend");
            }

            if (augendRowsIndexes == null)
            {
                throw new ArgumentNullException("augendRowsIndexes");
            }

            if (augendColumnsIndexes == null)
            {
                throw new ArgumentNullException("augendColumnsIndexes");
            }

            if (addend == null)
            {
                throw new ArgumentNullException("addend");
            }

#endif

            var augendRowsCount = augend.RowsCount;
            var augendColumnsCount = augend.ColumnsCount;

            var augendValues = augend.Values;
            var addendValues = addend.Values;

            var addendRowsCount = addend.RowsCount;

            var rowsToAdd = Math.Min(augendRowsIndexes.Length, addendRowsCount);

            var columnsToAdd = Math.Min(augendColumnsIndexes.Length, addend.ColumnsCount);
            for (var columnIndex = 0; columnIndex < columnsToAdd; columnIndex++)
            {
                var columnElementIndex = augendColumnsIndexes[columnIndex];

#if SAFE

                if ((columnElementIndex < 0) || (augendColumnsCount <= columnElementIndex))
                {
                    var argumentName = string.Format(CultureInfo.CurrentCulture, "augendColumnsIndexes[{0}]", columnIndex);
                    throw new ElementIndexIsOutOfRangeException(argumentName, columnElementIndex, 0, augendColumnsCount - 1);
                }

#endif

                var augendIndex = columnElementIndex * augendRowsCount;
                var addendIndex = columnIndex * addendRowsCount;

                for (var rowIndex = 0; rowIndex < rowsToAdd; rowIndex++)
                {
                    var rowElementIndex = augendRowsIndexes[rowIndex];

#if SAFE

                    if ((rowElementIndex < 0) || (augendRowsCount <= rowElementIndex))
                    {
                        var argumentName = string.Format(CultureInfo.CurrentCulture, "augendRowsIndexes[{0}]", rowIndex);
                        throw new ElementIndexIsOutOfRangeException(argumentName, rowElementIndex, 0, augendRowsCount - 1);
                    }

#endif

                    augendValues[augendIndex + rowElementIndex] += addendValues[addendIndex];

                    addendIndex++;
                }
            }
        }

        /// <summary>
        ///     Adds the second matrix multiplied by the factor to the specified elements of the first matrix.
        /// </summary>
        /// <param name="augend">
        ///     The augend matrix.
        /// </param>
        /// <param name="augendRowsIndexes">
        ///     The collection of zero-based indexes of rows of augend elements.
        /// </param>
        /// <param name="augendColumnsIndexes">
        ///     The collection of zero-based indexes of columns of augend elements.
        /// </param>
        /// <param name="addend">
        ///     The addend matrix.
        /// </param>
        /// <param name="addendFactor">
        ///     The factor of the addend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="augend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="addend"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="augendRowsIndexes"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="augendColumnsIndexes"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="ElementIndexIsOutOfRangeException">
        ///     An element of <paramref name="augendRowsIndexes"/> is negative or greater than or equal to
        ///     rows count of <paramref name="augend"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         An element of <paramref name="augendColumnsIndexes"/> is negative or greater than or equal to
        ///         columns count of <paramref name="augend"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="addend"/> to add is determined as
        ///     min(<paramref name="augendRowsIndexes"/>.Length, <paramref name="addend"/>.RowsCount).
        ///     The number of columns of <paramref name="addend"/> to add is determined as
        ///     min(<paramref name="augendColumnsIndexes"/>.Length, <paramref name="addend"/>.ColumnsCount).
        /// </remarks>
        public static void Add(
            this CMFlattenDenseMatrix<decimal> augend,
            int[] augendRowsIndexes,
            int[] augendColumnsIndexes,
            CMFlattenDenseMatrix<decimal> addend,
            decimal addendFactor)
        {
#if SAFE

            if (augend == null)
            {
                throw new ArgumentNullException("augend");
            }

            if (augendRowsIndexes == null)
            {
                throw new ArgumentNullException("augendRowsIndexes");
            }

            if (augendColumnsIndexes == null)
            {
                throw new ArgumentNullException("augendColumnsIndexes");
            }

            if (addend == null)
            {
                throw new ArgumentNullException("addend");
            }

#endif

            var augendRowsCount = augend.RowsCount;
            var augendColumnsCount = augend.ColumnsCount;

            var augendValues = augend.Values;
            var addendValues = addend.Values;

            var addendRowsCount = addend.RowsCount;

            var rowsToAdd = Math.Min(augendRowsIndexes.Length, addendRowsCount);

            var columnsToAdd = Math.Min(augendColumnsIndexes.Length, addend.ColumnsCount);
            for (var columnIndex = 0; columnIndex < columnsToAdd; columnIndex++)
            {
                var columnElementIndex = augendColumnsIndexes[columnIndex];

#if SAFE

                if ((columnElementIndex < 0) || (augendColumnsCount <= columnElementIndex))
                {
                    var argumentName = string.Format(CultureInfo.CurrentCulture, "augendColumnsIndexes[{0}]", columnIndex);
                    throw new ElementIndexIsOutOfRangeException(argumentName, columnElementIndex, 0, augendColumnsCount - 1);
                }

#endif

                var augendIndex = columnElementIndex * augendRowsCount;
                var addendIndex = columnIndex * addendRowsCount;

                for (var rowIndex = 0; rowIndex < rowsToAdd; rowIndex++)
                {
                    var rowElementIndex = augendRowsIndexes[rowIndex];

#if SAFE

                    if ((rowElementIndex < 0) || (augendRowsCount <= rowElementIndex))
                    {
                        var argumentName = string.Format(CultureInfo.CurrentCulture, "augendRowsIndexes[{0}]", rowIndex);
                        throw new ElementIndexIsOutOfRangeException(argumentName, rowElementIndex, 0, augendRowsCount - 1);
                    }

#endif

                    augendValues[augendIndex + rowElementIndex] += addendFactor * addendValues[addendIndex];

                    addendIndex++;
                }
            }
        }

        /// <summary>
        ///     Multiplies all elements of the matrix by the specified factor.
        /// </summary>
        /// <param name="matrix">
        ///     The matrix whose elements should be multiplied.
        /// </param>
        /// <param name="factor">
        ///     The factor to multiply.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="matrix"/> is <see langword="null"/>.
        /// </exception>
        public static void Multiply(this CMFlattenDenseMatrix<decimal> matrix, decimal factor)
        {
#if SAFE

            if (matrix == null)
            {
                throw new ArgumentNullException("matrix");
            }

#endif

            var values = matrix.Values;

            var elementsCount = matrix.RowsCount * matrix.ColumnsCount;
            for (var elementIndex = 0; elementIndex < elementsCount; elementIndex++)
            {
                values[elementIndex] *= factor;
            }
        }

        /// <summary>
        ///     Multiplies the matrix by the dense vector and stores the result in the another vector.
        /// </summary>
        /// <param name="multiplicand">
        ///     The multiplicand matrix.
        /// </param>
        /// <param name="multiplier">
        ///     The multiplier vector.
        /// </param>
        /// <param name="result">
        ///     The vector to store the result.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="multiplicand"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="multiplier"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="result"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="multiplicand"/> to multiply is determined as
        ///     min(<paramref name="multiplicand"/>.RowsCount, <paramref name="result"/>.Length).
        ///     The number of columns of <paramref name="multiplicand"/> to multiply is determined as
        ///     min(<paramref name="multiplicand"/>.ColumnsCount, <paramref name="multiplier"/>.Length).
        /// </remarks>
        public static void Multiply(
            this CMFlattenDenseMatrix<decimal> multiplicand, DenseVector<decimal> multiplier, DenseVector<decimal> result)
        {
#if SAFE

            if (multiplicand == null)
            {
                throw new ArgumentNullException("multiplicand");
            }

            if (multiplier == null)
            {
                throw new ArgumentNullException("multiplier");
            }

            if (result == null)
            {
                throw new ArgumentNullException("result");
            }

#endif

            var multiplicandValues = multiplicand.Values;
            var multiplierValues = multiplier.Values;
            var resultValues = result.Values;

            result.Clear();

            var multiplicandRowsCount = multiplicand.RowsCount;
            var rowsCountToMultiply = Math.Min(multiplicandRowsCount, result.Length);

            var columnsCountToMultiply = Math.Min(multiplicand.ColumnsCount, multiplier.Length);
            for (var columnIndex = 0; columnIndex < columnsCountToMultiply; columnIndex++)
            {
                var elementIndex = columnIndex * multiplicandRowsCount;

                var multiplierElement = multiplierValues[columnIndex];

                for (var rowIndex = 0; rowIndex < rowsCountToMultiply; rowIndex++)
                {
                    resultValues[rowIndex] += multiplicandValues[elementIndex] * multiplierElement;

                    elementIndex++;
                }
            }
        }

        /// <summary>
        ///     Subtracts the second matrix from the first matrix.
        /// </summary>
        /// <param name="minuend">
        ///     The minuend matrix.
        /// </param>
        /// <param name="subtrahend">
        ///     The subtrahend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="minuend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="subtrahend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="subtrahend"/> to subtract from <paramref name="minuend"/>
        ///     is determined as min(<paramref name="minuend"/>.RowsCount, <paramref name="subtrahend"/>.RowsCount).
        ///     The number of columns of <paramref name="subtrahend"/> to subtract from <paramref name="minuend"/>
        ///     is determined as min(<paramref name="minuend"/>.ColumnsCount, <paramref name="subtrahend"/>.ColumnsCount).
        /// </remarks>
        public static void Subtract(this CMFlattenDenseMatrix<decimal> minuend, CMFlattenDenseMatrix<decimal> subtrahend)
        {
#if SAFE

            if (minuend == null)
            {
                throw new ArgumentNullException("minuend");
            }

            if (subtrahend == null)
            {
                throw new ArgumentNullException("subtrahend");
            }

#endif

            var minuendValues = minuend.Values;
            var subtrahendValues = subtrahend.Values;
            var minuendRowsCount = minuend.RowsCount;
            var subtrahendRowsCount = subtrahend.RowsCount;
            var rowsCountToSubtract = Math.Min(minuendRowsCount, subtrahendRowsCount);

            var columnsCountToSubtract = Math.Min(minuend.ColumnsCount, subtrahend.ColumnsCount);
            for (var columnIndex = 0; columnIndex < columnsCountToSubtract; columnIndex++)
            {
                var subtrahendElementIndex = subtrahendRowsCount * columnIndex;

                var minuendElementIndex = minuendRowsCount * columnIndex;
                var columnEnd = minuendElementIndex + rowsCountToSubtract;
                while (minuendElementIndex < columnEnd)
                {
                    minuendValues[minuendElementIndex] -= subtrahendValues[subtrahendElementIndex];

                    minuendElementIndex++;
                    subtrahendElementIndex++;
                }
            }
        }

        /// <summary>
        ///     Subtracts the second matrix from the first matrix multiplied by the factor.
        /// </summary>
        /// <param name="minuend">
        ///     The minuend matrix.
        /// </param>
        /// <param name="minuendFactor">
        ///     The factor of the minuend matrix.
        /// </param>
        /// <param name="subtrahend">
        ///     The subtrahend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="minuend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="subtrahend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="subtrahend"/> to subtract from <paramref name="minuend"/>
        ///     is determined as min(<paramref name="minuend"/>.RowsCount, <paramref name="subtrahend"/>.RowsCount).
        ///     The number of columns of <paramref name="subtrahend"/> to subtract from <paramref name="minuend"/>
        ///     is determined as min(<paramref name="minuend"/>.ColumnsCount, <paramref name="subtrahend"/>.ColumnsCount).
        /// </remarks>
        public static void Subtract(
            this CMFlattenDenseMatrix<decimal> minuend, decimal minuendFactor, CMFlattenDenseMatrix<decimal> subtrahend)
        {
#if SAFE

            if (minuend == null)
            {
                throw new ArgumentNullException("minuend");
            }

            if (subtrahend == null)
            {
                throw new ArgumentNullException("subtrahend");
            }

#endif

            var minuendValues = minuend.Values;
            var subtrahendValues = subtrahend.Values;
            var minuendRowsCount = minuend.RowsCount;
            var subtrahendRowsCount = subtrahend.RowsCount;
            var rowsCountToSubtract = Math.Min(minuendRowsCount, subtrahendRowsCount);

            var columnsCountToSubtract = Math.Min(minuend.ColumnsCount, subtrahend.ColumnsCount);
            for (var columnIndex = 0; columnIndex < columnsCountToSubtract; columnIndex++)
            {
                var subtrahendElementIndex = subtrahendRowsCount * columnIndex;

                var minuendElementIndex = minuendRowsCount * columnIndex;
                var columnEnd = minuendElementIndex + rowsCountToSubtract;
                while (minuendElementIndex < columnEnd)
                {
                    minuendValues[minuendElementIndex] =
                        (minuendFactor * minuendValues[minuendElementIndex]) - subtrahendValues[subtrahendElementIndex];

                    minuendElementIndex++;
                    subtrahendElementIndex++;
                }
            }
        }

        /// <summary>
        ///     Subtracts the second matrix multiplied by the factor from the first matrix.
        /// </summary>
        /// <param name="minuend">
        ///     The minuend matrix.
        /// </param>
        /// <param name="subtrahend">
        ///     The subtrahend matrix.
        /// </param>
        /// <param name="subtrahendFactor">
        ///     The factor of the subtrahend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="minuend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="subtrahend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="subtrahend"/> to subtract from <paramref name="minuend"/>
        ///     is determined as min(<paramref name="minuend"/>.RowsCount, <paramref name="subtrahend"/>.RowsCount).
        ///     The number of columns of <paramref name="subtrahend"/> to subtract from <paramref name="minuend"/>
        ///     is determined as min(<paramref name="minuend"/>.ColumnsCount, <paramref name="subtrahend"/>.ColumnsCount).
        /// </remarks>
        public static void Subtract(
            this CMFlattenDenseMatrix<decimal> minuend, CMFlattenDenseMatrix<decimal> subtrahend, decimal subtrahendFactor)
        {
#if SAFE

            if (minuend == null)
            {
                throw new ArgumentNullException("minuend");
            }

            if (subtrahend == null)
            {
                throw new ArgumentNullException("subtrahend");
            }

#endif

            var minuendValues = minuend.Values;
            var subtrahendValues = subtrahend.Values;
            var minuendRowsCount = minuend.RowsCount;
            var subtrahendRowsCount = subtrahend.RowsCount;
            var rowsCountToSubtract = Math.Min(minuendRowsCount, subtrahendRowsCount);

            var columnsCountToSubtract = Math.Min(minuend.ColumnsCount, subtrahend.ColumnsCount);
            for (var columnIndex = 0; columnIndex < columnsCountToSubtract; columnIndex++)
            {
                var subtrahendElementIndex = subtrahendRowsCount * columnIndex;

                var minuendElementIndex = minuendRowsCount * columnIndex;
                var columnEnd = minuendElementIndex + rowsCountToSubtract;
                while (minuendElementIndex < columnEnd)
                {
                    minuendValues[minuendElementIndex] -= subtrahendFactor * subtrahendValues[subtrahendElementIndex];

                    minuendElementIndex++;
                    subtrahendElementIndex++;
                }
            }
        }

        /// <summary>
        ///     Subtracts the second matrix multiplied by the second factor from the first matrix multiplied by the first factor.
        /// </summary>
        /// <param name="minuend">
        ///     The minuend matrix.
        /// </param>
        /// <param name="minuendFactor">
        ///     The factor of the minuend matrix.
        /// </param>
        /// <param name="subtrahend">
        ///     The subtrahend matrix.
        /// </param>
        /// <param name="subtrahendFactor">
        ///     The factor of the subtrahend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="minuend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="subtrahend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="subtrahend"/> to subtract from <paramref name="minuend"/>
        ///     is determined as min(<paramref name="minuend"/>.RowsCount, <paramref name="subtrahend"/>.RowsCount).
        ///     The number of columns of <paramref name="subtrahend"/> to subtract from <paramref name="minuend"/>
        ///     is determined as min(<paramref name="minuend"/>.ColumnsCount, <paramref name="subtrahend"/>.ColumnsCount).
        /// </remarks>
        public static void Subtract(
            this CMFlattenDenseMatrix<decimal> minuend,
            decimal minuendFactor,
            CMFlattenDenseMatrix<decimal> subtrahend,
            decimal subtrahendFactor)
        {
#if SAFE

            if (minuend == null)
            {
                throw new ArgumentNullException("minuend");
            }

            if (subtrahend == null)
            {
                throw new ArgumentNullException("subtrahend");
            }

#endif

            var minuendValues = minuend.Values;
            var subtrahendValues = subtrahend.Values;
            var minuendRowsCount = minuend.RowsCount;
            var subtrahendRowsCount = subtrahend.RowsCount;
            var rowsCountToSubtract = Math.Min(minuendRowsCount, subtrahendRowsCount);

            var columnsCountToSubtract = Math.Min(minuend.ColumnsCount, subtrahend.ColumnsCount);
            for (var columnIndex = 0; columnIndex < columnsCountToSubtract; columnIndex++)
            {
                var subtrahendElementIndex = subtrahendRowsCount * columnIndex;

                var minuendElementIndex = minuendRowsCount * columnIndex;
                var columnEnd = minuendElementIndex + rowsCountToSubtract;
                while (minuendElementIndex < columnEnd)
                {
                    minuendValues[minuendElementIndex] =
                        (minuendFactor * minuendValues[minuendElementIndex]) -
                        (subtrahendFactor * subtrahendValues[subtrahendElementIndex]);

                    minuendElementIndex++;
                    subtrahendElementIndex++;
                }
            }
        }

        /// <summary>
        ///     Subtracts the second matrix from the specified elements of the first matrix.
        /// </summary>
        /// <param name="minuend">
        ///     The minuend matrix.
        /// </param>
        /// <param name="minuendRowsIndexes">
        ///     The collection of zero-based indexes of rows of minuend elements.
        /// </param>
        /// <param name="minuendColumnsIndexes">
        ///     The collection of zero-based indexes of columns of minuend elements.
        /// </param>
        /// <param name="subtrahend">
        ///     The subtrahend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="minuend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="subtrahend"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="minuendRowsIndexes"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="minuendColumnsIndexes"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="ElementIndexIsOutOfRangeException">
        ///     An element of <paramref name="minuendRowsIndexes"/> is negative or greater than or equal to
        ///     rows count of <paramref name="minuend"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         An element of <paramref name="minuendColumnsIndexes"/> is negative or greater than or equal to
        ///         columns count of <paramref name="minuend"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="subtrahend"/> to subtract is determined as
        ///     min(<paramref name="minuendRowsIndexes"/>.Length, <paramref name="subtrahend"/>.RowsCount).
        ///     The number of columns of <paramref name="subtrahend"/> to subtract is determined as
        ///     min(<paramref name="minuendColumnsIndexes"/>.Length, <paramref name="subtrahend"/>.ColumnsCount).
        /// </remarks>
        public static void Subtract(
            this CMFlattenDenseMatrix<decimal> minuend,
            int[] minuendRowsIndexes,
            int[] minuendColumnsIndexes,
            CMFlattenDenseMatrix<decimal> subtrahend)
        {
#if SAFE

            if (minuend == null)
            {
                throw new ArgumentNullException("minuend");
            }

            if (minuendRowsIndexes == null)
            {
                throw new ArgumentNullException("minuendRowsIndexes");
            }

            if (minuendColumnsIndexes == null)
            {
                throw new ArgumentNullException("minuendColumnsIndexes");
            }

            if (subtrahend == null)
            {
                throw new ArgumentNullException("subtrahend");
            }

#endif

            var minuendRowsCount = minuend.RowsCount;
            var minuendColumnsCount = minuend.ColumnsCount;

            var minuendValues = minuend.Values;
            var subtrahendValues = subtrahend.Values;

            var subtrahendRowsCount = subtrahend.RowsCount;

            var rowsCountToSubtract = Math.Min(minuendRowsIndexes.Length, subtrahendRowsCount);

            var columnsCountToSubtract = Math.Min(minuendColumnsIndexes.Length, subtrahend.ColumnsCount);
            for (var columnIndex = 0; columnIndex < columnsCountToSubtract; columnIndex++)
            {
                var columnElementIndex = minuendColumnsIndexes[columnIndex];

#if SAFE

                if ((columnElementIndex < 0) || (minuendColumnsCount <= columnElementIndex))
                {
                    var argumentName = string.Format(CultureInfo.CurrentCulture, "minuendColumnsIndexes[{0}]", columnIndex);
                    throw new ElementIndexIsOutOfRangeException(argumentName, columnElementIndex, 0, minuendColumnsCount - 1);
                }

#endif

                var minuendIndex = columnElementIndex * minuendRowsCount;
                var subtrahendIndex = columnIndex * subtrahendRowsCount;

                for (var rowIndex = 0; rowIndex < rowsCountToSubtract; rowIndex++)
                {
                    var rowElementIndex = minuendRowsIndexes[rowIndex];

#if SAFE

                    if ((rowElementIndex < 0) || (minuendRowsCount <= rowElementIndex))
                    {
                        var argumentName = string.Format(
                            CultureInfo.CurrentCulture, "minuendRowsIndexes[{0}]", rowIndex);
                        throw new ElementIndexIsOutOfRangeException(argumentName, rowElementIndex, 0, minuendRowsCount - 1);
                    }

#endif

                    minuendValues[minuendIndex + rowElementIndex] -= subtrahendValues[subtrahendIndex];

                    subtrahendIndex++;
                }
            }
        }

        /// <summary>
        ///     Subtracts the second matrix multiplied by the factor from the specified elements of the first matrix.
        /// </summary>
        /// <param name="minuend">
        ///     The  minuend matrix.
        /// </param>
        /// <param name="minuendRowsIndexes">
        ///     The collection of zero-based indexes of rows of minuend elements.
        /// </param>
        /// <param name="minuendColumnsIndexes">
        ///     The collection of zero-based indexes of columns of minuend elements.
        /// </param>
        /// <param name="subtrahend">
        ///     The subtrahend matrix.
        /// </param>
        /// <param name="subtrahendFactor">
        ///     The factor of the subtrahend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="minuend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="subtrahend"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="minuendRowsIndexes"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="minuendColumnsIndexes"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="ElementIndexIsOutOfRangeException">
        ///     An element of <paramref name="minuendRowsIndexes"/> is negative or greater than or equal to
        ///     rows count of <paramref name="minuend"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         An element of <paramref name="minuendColumnsIndexes"/> is negative or greater than or equal to
        ///         columns count of <paramref name="minuend"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="subtrahend"/> to subtract is determined as
        ///     min(<paramref name="minuendRowsIndexes"/>.Length, <paramref name="subtrahend"/>.RowsCount).
        ///     The number of columns of <paramref name="subtrahend"/> to subtract is determined as
        ///     min(<paramref name="minuendColumnsIndexes"/>.Length, <paramref name="subtrahend"/>.ColumnsCount).
        /// </remarks>
        public static void Subtract(
            this CMFlattenDenseMatrix<decimal> minuend,
            int[] minuendRowsIndexes,
            int[] minuendColumnsIndexes,
            CMFlattenDenseMatrix<decimal> subtrahend,
            decimal subtrahendFactor)
        {
#if SAFE

            if (minuend == null)
            {
                throw new ArgumentNullException("minuend");
            }

            if (minuendRowsIndexes == null)
            {
                throw new ArgumentNullException("minuendRowsIndexes");
            }

            if (minuendColumnsIndexes == null)
            {
                throw new ArgumentNullException("minuendColumnsIndexes");
            }

            if (subtrahend == null)
            {
                throw new ArgumentNullException("subtrahend");
            }

#endif

            var minuendRowsCount = minuend.RowsCount;
            var minuendColumnsCount = minuend.ColumnsCount;

            var minuendValues = minuend.Values;
            var subtrahendValues = subtrahend.Values;

            var subtrahendRowsCount = subtrahend.RowsCount;

            var rowsCountToSubtract = Math.Min(minuendRowsIndexes.Length, subtrahendRowsCount);

            var columnsCountToSubtract = Math.Min(minuendColumnsIndexes.Length, subtrahend.ColumnsCount);
            for (var columnIndex = 0; columnIndex < columnsCountToSubtract; columnIndex++)
            {
                var columnElementIndex = minuendColumnsIndexes[columnIndex];

#if SAFE

                if ((columnElementIndex < 0) || (minuendColumnsCount <= columnElementIndex))
                {
                    var argumentName = string.Format(CultureInfo.CurrentCulture, "minuendColumnsIndexes[{0}]", columnIndex);
                    throw new ElementIndexIsOutOfRangeException(argumentName, columnElementIndex, 0, minuendColumnsCount - 1);
                }

#endif

                var minuendIndex = columnElementIndex * minuendRowsCount;
                var subtrahendIndex = columnIndex * subtrahendRowsCount;

                for (var rowIndex = 0; rowIndex < rowsCountToSubtract; rowIndex++)
                {
                    var rowElementIndex = minuendRowsIndexes[rowIndex];

#if SAFE

                    if ((rowElementIndex < 0) || (minuendRowsCount <= rowElementIndex))
                    {
                        var argumentName = string.Format(
                            CultureInfo.CurrentCulture, "minuendRowsIndexes[{0}]", rowIndex);
                        throw new ElementIndexIsOutOfRangeException(argumentName, rowElementIndex, 0, minuendRowsCount - 1);
                    }

#endif

                    minuendValues[minuendIndex + rowElementIndex] -= subtrahendFactor * subtrahendValues[subtrahendIndex];

                    subtrahendIndex++;
                }
            }
        }

        #endregion

        #endregion
    }
}